//
// Created by LiuYou on 2021/8/17.
//

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cassert>
#include <sys/epoll.h>
#include <fcntl.h>
#include <csignal>
#include <cerrno>
#include <unistd.h>
#include "TimerLinkedList.hpp"


static const int MAX_EVENT_NUMBER = 1024;
static const int FD_LIMIT = 65535;
static const int TIME_SLOT = 5;
static int pipeFd[2];
static int epollFd;
static TimerLinkedList timerLinkedList;

static int setFdNonBlocking(int fd) {
    int oldOption = fcntl( fd, F_GETFL );
    int newOption = oldOption | O_NONBLOCK;
    fcntl( fd, F_SETFL, newOption );
    return oldOption;
}

static void addFdEventToEpoll(int fd, int epollFd) {
    epoll_event epollEvent{};
    epollEvent.data.fd = fd;
    epollEvent.events = EPOLLIN | EPOLLET;
    setFdNonBlocking( fd );
    epoll_ctl( epollFd, EPOLL_CTL_ADD, fd, &epollEvent );
}

static void sigHandler(int sig) {
    int saveErrno = errno;
    send( pipeFd[1], (char*) &sig, 1, 0 );
    errno = saveErrno;
}

static void addSignal(int sig) {
    struct sigaction sigAction{};
    sigAction.sa_handler = sigHandler;
    sigAction.sa_flags |= SA_RESTART;
    sigfillset( &sigAction.sa_mask );
    sigaction( sig, &sigAction, nullptr );
    // 判断
}

static void cb_func(ClientData* clientData) {
    epoll_ctl( epollFd, EPOLL_CTL_DEL, clientData->socketFd, 0 );
    assert( clientData );
    close( clientData->socketFd );
    printf( "close fd %d\n", clientData->socketFd );
}

static void timerHandler() {
    timerLinkedList.tick();
    alarm( TIME_SLOT );
}

int main(int argc, char** argv) {
    if (argc < 3) {
        printf( "用法: %s ip port\n", basename( argv[0] ));
        exit( 1 );
    }
    char* ip = argv[1];
    int port = atoi( argv[2] );

    sockaddr_in localAddress{};
    localAddress.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &localAddress.sin_addr );
    localAddress.sin_port = htons( port );

    int listenFd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenFd >= 0 );
    int ret = bind( listenFd, (sockaddr*) &localAddress, sizeof( localAddress ));
    assert( ret != -1 );

    ret = listen( listenFd, 5 );
    assert( ret != -1 );

    epoll_event epollEvents[MAX_EVENT_NUMBER];
    epollFd = epoll_create( 5 );
    assert( epollFd != -1 );

    addFdEventToEpoll( listenFd, epollFd );

    ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipeFd );
    assert( ret != -1 );
    setFdNonBlocking( pipeFd[1] );
    addFdEventToEpoll( pipeFd[0], epollFd );

    addSignal( SIGALRM );
    addSignal( SIGTERM );
    bool stopServer = false;

    ClientData* ptrClientData = new ClientData[FD_LIMIT];
    bool timeout = false;
    alarm( TIME_SLOT );

    while (!stopServer) {
        int readyFdNumber = epoll_wait( epollFd, epollEvents, MAX_EVENT_NUMBER, -1 );
        if ((readyFdNumber < 0) && (errno != EINTR)) {
            printf( "epoll failure\n" );
            break;
        }

        for (int i = 0; i < readyFdNumber; ++i) {
            int fd = epollEvents[i].data.fd;
            if (fd == listenFd) {
                sockaddr_in remoteAddress{};
                socklen_t socklen = sizeof( remoteAddress );
                int connectFd = accept( listenFd, (sockaddr*) &remoteAddress, &socklen );
                addFdEventToEpoll( connectFd, epollFd );
                ptrClientData[connectFd].clientAddress = remoteAddress;
                ptrClientData[connectFd].socketFd = connectFd;
                UtilityTimer* ptrTimer = new UtilityTimer();
                ptrTimer->clientData = &ptrClientData[connectFd];
                ptrTimer->cb_func = cb_func;
                time_t currentTime = time( nullptr );
                ptrTimer->expire = currentTime + 3 * TIME_SLOT;
                ptrClientData[connectFd].utilityTimer = ptrTimer;
                timerLinkedList.addTimer( ptrTimer );
            } else if ((fd == pipeFd[0]) && (epollEvents[i].events & EPOLLIN)) {
                char signals[1024];
                ssize_t rcvBytes = recv( pipeFd[0], signals, sizeof( signals ), 0 );
                if (rcvBytes == -1) {
                    // handle the error
                    continue;
                } else if (rcvBytes == 0) {
                    continue;
                } else {
                    for (int j = 0; j < rcvBytes; ++j) {
                        switch (signals[j]) {
                            case SIGALRM: {
                                timeout = true;
                                break;
                            }
                            case SIGTERM: {
                                stopServer = true;
                            }
                        }
                    }
                }
            } else if (epollEvents[i].events & EPOLLIN) {
                memset( ptrClientData[fd].buf, '\0', ClientData::BUF_SIZE );
                ssize_t rcvBytes = recv( fd, ptrClientData[fd].buf, ClientData::BUF_SIZE - 1, 0 );
                printf( "get %zd bytes of client data %s from %d\n",
                        rcvBytes, ptrClientData[fd].buf, fd );
                UtilityTimer* timer = ptrClientData[fd].utilityTimer;
                if (rcvBytes < 0) {
                    if (errno != EAGAIN) {
                        cb_func( &ptrClientData[fd] );
                        if (timer) {
                            timerLinkedList.deleteTimer( timer );
                        }
                    }
                } else if (rcvBytes == 0) {
                    cb_func( &ptrClientData[fd] );
                    if (timer) {
                        timerLinkedList.deleteTimer( timer );
                    }
                } else {
                    if (timer) {
                        time_t currentTime = time( nullptr );
                        timer->expire = currentTime + 3 * TIME_SLOT;
                        printf( "adjust timer once\n" );
                        timerLinkedList.adjustTimer( timer );
                    }
                }
            } else {
                // others
            }
        }

        if (timeout) {
            timerHandler();
            timeout = false;
        }
    }

    close( listenFd );
    close( pipeFd[1] );
    close( pipeFd[0] );
    delete[] ptrClientData;

    exit( 0 );
}